#include "ffb.h"
#include <dinput.h>

#define MAX_JOYSTICKS   32
#define kDI_MaxEffects  30       // Hard coded limit (not a DX limit)

extern LPDIRECTINPUTDEVICE7 Joysticks[MAX_JOYSTICKS];

// Force Feedback Effect Data
// --------------------------
REFGUID effectGUID[kMaxEffectSubTypes] =
{
	&GUID_ConstantForce,
	&GUID_RampForce,
	&GUID_CustomForce,
	// period
	&GUID_Square,
	&GUID_Sine,
	&GUID_Triangle,
	&GUID_SawtoothUp,
	&GUID_SawtoothDown,
	// condition
	&GUID_Spring,
	&GUID_Damper,
	&GUID_Inertia,
	&GUID_Friction
};

typedef union {
	DICONSTANTFORCE constant;
	DIRAMPFORCE ramp;
	DIPERIODIC period;
	DICONDITION condition;
	DICUSTOMFORCE custom;
}tEffectClasses;

typedef struct tEffect {
	DIEFFECT general;
	tEffectClasses specific;
	DIENVELOPE envelope;
	LONG direction[2];
}tEffect;

static tEffect Effect[kDI_MaxEffects];

// DInput Data
// -----------
static LPDIRECTINPUTEFFECT DIE_hEffect[kDI_MaxEffects];
static int numEffects = 0;

// ---------
// ffb_Pause
// -------------------------------------------------------------------
// Purpose:
//    Pause the FFB output on the given device.  Use ffb_Continue to
//    continue where you left off.
// -------------------------------------------------------------------
void
ffb_Pause(short dev) {
	IDirectInputDevice7_SendForceFeedbackCommand(Joysticks[dev], DISFFC_PAUSE);
}

// ------------
// ffb_Continue
// -------------------------------------------------------------------
// Purpose:
//    Unpause the FFB output on the given device.  Complimentary to
//    ffb_Pause.
// -------------------------------------------------------------------
void
ffb_Continue(short dev) {
	IDirectInputDevice7_SendForceFeedbackCommand(Joysticks[dev], DISFFC_CONTINUE);
}

// ----------
// ffb_Enable
// -------------------------------------------------------------------
// Purpose:
//    Must be called after initialization in order to activate the
//    device.
//    Use ffb_Pause & ffb_Continue if you want disable forces
//    temporarily and resume later.
// -------------------------------------------------------------------
void
ffb_Enable(short dev) {
	IDirectInputDevice7_SendForceFeedbackCommand(Joysticks[dev], DISFFC_SETACTUATORSON);
}

// -----------
// ffb_Disable
// -------------------------------------------------------------------
// Purpose:
//    Turns off FFB, but effects still play on processor.
// -------------------------------------------------------------------
void
ffb_Disable(short dev) {
	IDirectInputDevice7_SendForceFeedbackCommand(Joysticks[dev], DISFFC_SETACTUATORSOFF);
}

void
ffb_simpleJolt(short dev) {
	tFFB_Effect effect;
	short eID;
	// Constant Effect (simple jolt)
	// -----------------------------
	effect.Type = kConstant;
	effect.TypeInfo.Constant.Mag = 10000;
	effect.Duration = 1000000 >> 1;             // half second
	effect.Period = 1000;
	effect.Gain = 10000;
	effect.Trigger = kNoButton;
	effect.TriggerRepeatTime = 0;
	effect.Direction = 0;

	eID = ffb_effectCreate(dev, &effect);
}

// ----------------
// ffb_effectCreate
// -------------------------------------------------------------------
// Purpose:
//    Create a single effect for future playback.
//    Effect is given a logical ID
// -------------------------------------------------------------------
int
ffb_effectCreate(short dev, tFFB_Effect* eff) {
	HRESULT hr;

	// Important stuff
	Effect[numEffects].general.dwDuration = eff->Duration;
	Effect[numEffects].general.dwSamplePeriod = eff->Period;
	Effect[numEffects].general.dwGain = eff->Gain;
	Effect[numEffects].general.dwTriggerButton = (eff->Trigger == kNoButton ? DIEB_NOTRIGGER : DIJOFS_BUTTON(eff->Trigger));
	Effect[numEffects].general.dwTriggerRepeatInterval = eff->TriggerRepeatTime;
	Effect[numEffects].direction[0] = eff->Direction;
	memcpy(&Effect[numEffects].specific, &eff->TypeInfo, sizeof(tEffectClasses));

	// Wacky COM related overhead
	Effect[numEffects].general.dwSize = sizeof(DIEFFECT);
	Effect[numEffects].general.cAxes = 2;
	Effect[numEffects].general.rgdwAxes = 0;
	Effect[numEffects].general.dwFlags = DIEFF_POLAR | DIEFF_OBJECTOFFSETS;
	Effect[numEffects].general.lpvTypeSpecificParams = &Effect[numEffects].specific;
	Effect[numEffects].general.rglDirection = &Effect[numEffects].direction;
	Effect[numEffects].direction[1] = 0L;
	Effect[numEffects].general.lpEnvelope = NULL;
	Effect[numEffects].general.lpvTypeSpecificParams = &Effect[numEffects].specific;


	switch (eff->Type) {
	case kConstant:
		Effect[numEffects].general.cbTypeSpecificParams = sizeof(DICONSTANTFORCE);
		break;
	case kRamp:
		Effect[numEffects].general.cbTypeSpecificParams = sizeof(DIRAMPFORCE);
		break;
	case kCustom:
		Effect[numEffects].general.cbTypeSpecificParams = sizeof(DICUSTOMFORCE);
		break;
	case kWave_Square:
	case kWave_Sine:
	case kWave_Triange:
	case kWave_SawUp:
	case kWave_SawDown:
		Effect[numEffects].general.cbTypeSpecificParams = sizeof(DIPERIODIC);
		break;
	case kCondition_Spring:
	case kCondition_Damper:
	case kCondition_Inertia:
	case kCondition_Friction:
		Effect[numEffects].general.cbTypeSpecificParams = sizeof(DICONDITION);
		break;
	default:
		return(-1);
	}


	hr = IDirectInputDevice7_CreateEffect(Joysticks[dev],
										  effectGUID[eff->Type],
										  (LPCDIEFFECT)&Effect[numEffects],
										  (LPDIRECTINPUTEFFECT*)&DIE_hEffect[numEffects], 0);
	switch (hr) {
	case DI_OK:
		ffb_effectUnload(numEffects);
		++numEffects;
		return(numEffects - 1);

	case DIERR_DEVICENOTREG:
		return(-1);

	case DIERR_DEVICEFULL:
		return(-1);
	}
	return(-1);
}

// --------------
// ffb_effectPlay
// -------------------------------------------------------------------
// Purpose:
//    Play an effect that was previously created.
// -------------------------------------------------------------------
void
ffb_effectPlay(short eID) {
	IDirectInputEffect_Start(DIE_hEffect[eID], 1, 0);
}

// --------------
// ffb_effectStop
// -------------------------------------------------------------------
// Purpose:
//    Stop a single effect.
// -------------------------------------------------------------------
void
ffb_effectStop(short eID) {
	IDirectInputEffect_Stop(DIE_hEffect[eID]);
}

// -----------------
// ffb_effectStopAll
// -------------------------------------------------------------------
// Purpose:
//    Stops all forces on the given device.
// -------------------------------------------------------------------
void
ffb_effectStopAll(short dev) {
	IDirectInputDevice7_SendForceFeedbackCommand(Joysticks[dev], DISFFC_STOPALL);
}

// ----------------
// ffb_effectUnload
// -------------------------------------------------------------------
// Purpose:
//    Unload a single effect...  Necessary to make room for other
//    effects.
// -------------------------------------------------------------------
void
ffb_effectUnload(short eID) {
	IDirectInputEffect_Unload(DIE_hEffect[eID]);
}


// ----------------
// ffb_effectModify
// -------------------------------------------------------------------
// Purpose:
//    Modifies a single effect, only if the given parameters are
//    different from what's currently loaded.
// -------------------------------------------------------------------
void
ffb_effectModify(short eID, int*        Direction, int* Duration, int* Gain,
				 int* Period, tEffInfo* TypeInfo, tEffEnvelope* Envelope) {
	int flags = 0;

	return;
	if (Direction) {
		if (Effect[eID].direction[0] != *Direction) {
			Effect[eID].direction[0] = *Direction;
			flags |= DIEP_DIRECTION;
		}
	}
	if (Duration) {
		if (Effect[eID].general.dwDuration != *Duration) {
			Effect[eID].general.dwDuration = *Duration;
			flags |= DIEP_DURATION;
		}
	}
	if (Gain) {
		if (Effect[eID].general.dwGain != *Gain) {
			Effect[eID].general.dwGain = *Gain;
			flags |= DIEP_GAIN;
		}
	}
	if (Period) {
		if (Effect[eID].general.dwSamplePeriod != *Period) {
			Effect[eID].general.dwSamplePeriod = *Period;
			flags |= DIEP_SAMPLEPERIOD;
		}
	}
	if (TypeInfo) {
		if (!memcmp(&Effect[eID].specific, TypeInfo, Effect[eID].general.cbTypeSpecificParams)) {
			memcpy(&Effect[eID].specific, TypeInfo, Effect[eID].general.cbTypeSpecificParams);
			flags |= DIEP_TYPESPECIFICPARAMS;
		}
	}
	if (Envelope) {
		if (Effect[eID].envelope.dwAttackLevel != Envelope->AttackLevel ||
			Effect[eID].envelope.dwAttackTime != Envelope->AttackTime ||
			Effect[eID].envelope.dwFadeLevel != Envelope->FadeLevel ||
			Effect[eID].envelope.dwFadeTime != Envelope->FadeTime) {
			Effect[eID].envelope.dwAttackLevel = Envelope->AttackLevel;
			Effect[eID].envelope.dwAttackTime = Envelope->AttackTime;
			Effect[eID].envelope.dwFadeLevel = Envelope->FadeLevel;
			Effect[eID].envelope.dwFadeTime = Envelope->FadeTime;
			flags |= DIEP_ENVELOPE;
		}
	}

	if (!flags)
		return;

	IDirectInputEffect_SetParameters(DIE_hEffect[eID], (LPCDIEFFECT)&Effect[eID], flags);
}
